﻿using System;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Windows;

namespace OpenLS.Spreadsheet
{
    static class RangeNavigator
    {
        public static Range GetSelectedRange(Range range, Cell activeCell, 
            BlockNavigationMode blockNavigationMode, NavigationDirection navigationDirection, out CellAddress newAddress)
        {
            newAddress = new CellAddress();
            if (range == null)
                return null;
            Worksheet ws = range.Worksheet;
            newAddress = activeCell.Address;

            if (range is FullRowRange)
            {
                var r = (FullRowRange)range;
                int row = newAddress.Row;
                if (row == r.FirstRow.Index)
                    row = r.LastRow.Index;
                else
                    row = r.FirstRow.Index;
                newAddress = new CellAddress(row, 0);
                newAddress = newAddress.GetNeighbor(navigationDirection);
                return new FullRowRange(ws.Rows[newAddress.Row], activeCell.Row);
            }
            if (range is FullColumnRange)
            {
                var r = (FullColumnRange)range;
                int column = newAddress.Column;
                if (column == r.FirstColumn.Index)
                    column = r.LastColumn.Index;
                else
                    column = r.FirstColumn.Index;
                newAddress = new CellAddress(0, column);
                newAddress = newAddress.GetNeighbor(navigationDirection);
                return new FullColumnRange(ws.Columns[newAddress.Column], activeCell.Column);
            }
            newAddress = GetOpposite(range, newAddress);
            CellsRange result;
            //Address oldAddress = a;
            if (blockNavigationMode == BlockNavigationMode.ByCell)
            {
                newAddress = newAddress.GetNeighbor(navigationDirection);
                while (!ws[newAddress].IsVisible && !newAddress.IsAtEnd(navigationDirection))
                    newAddress = newAddress.GetNeighbor(navigationDirection);

                result = CellsRange.Create(ws, new RangeAddress(activeCell.Address, newAddress));
            }
            else
            {
                var newCell = GetVisibleNeighbor(ws[newAddress], navigationDirection, blockNavigationMode);
                if (newCell != null)
                    newAddress = newCell.Address;
                else
                {
                    //\\newAddress = activeCell.Address;
                }

                result = CellsRange.Create(ws, new RangeAddress(activeCell.Address, newAddress));
            }
            return result;

        }
        private static Cell GetNeighbor(Cell c, NavigationDirection navigationDirection, BlockNavigationMode mode)
        {
            Contract.Requires(c != null);
            Contract.Ensures(Contract.Result<Cell>() != c);
            Worksheet ws = c.Worksheet;
            bool oldEmpty = c.Value == null;
            c = c.GetNeighbor(navigationDirection);
            if (c == null)
                return null;
            if (mode == BlockNavigationMode.ByBlock)
            {
                if (oldEmpty)
                    while (c.Value == null)
                    {
                        if (navigationDirection == NavigationDirection.Down && c.Row.Index > ws.Bottom)
                            break;
                        if (navigationDirection == NavigationDirection.Right && c.Column.Index > ws.Right)
                            break;
                        Cell newC = c.GetNeighbor(navigationDirection);
                        if (newC == c)
                            break;
                        c = newC;
                        if (c == null)
                            break;
                    }
                else
                {
                    Contract.Assert (c != null);
                    while (c.GetNeighbor(navigationDirection) != null &&
                           c.GetNeighbor(navigationDirection).Value != null)
                    {
                        if (navigationDirection == NavigationDirection.Down && c.Row.Index > ws.Bottom)
                            break;
                        if (navigationDirection == NavigationDirection.Right && c.Column.Index > ws.Right)
                            break;
                        Cell newC = c.GetNeighbor(navigationDirection);
                        if (newC == c)
                            break;
                        c = newC;
                        if (c == null)
                            break;
                    }
                }
            }
            return c;
        }


        public static Cell GetVisibleNeighbor(Cell cell, NavigationDirection navigationDirection, BlockNavigationMode mode)
        {
            Contract.Requires(cell != null);
            Contract.Ensures(!(cell.IsVisible) || Contract.Result<Cell>() == null || Contract.Result<Cell>().IsVisible);
            var result = GetNeighbor(cell, navigationDirection, mode);
            if (result == null)
                return result;
            if (result.IsVisible)
                return result;
            var r = result;
            Func<Cell, bool> isVisibleFunc = c =>
                                                 {
                                                     switch (navigationDirection)
                                                     {
                                                         case NavigationDirection.Down:
                                                         case NavigationDirection.Up:
                                                             return r.Row.IsVisible;
                                                         case NavigationDirection.Left:
                                                         case NavigationDirection.Right:
                                                             return r.Column.IsVisible;
                                                         default:
                                                             throw new NotSupportedException();
                                                     }
                                                 };
            while (r != null && !isVisibleFunc(r))
            {
                r = GetNeighbor(r, navigationDirection, BlockNavigationMode.ByCell);
                if (r == null && result.IsVisible)
                    return result;
            }
            if (r != null && !isVisibleFunc(r))
                return null;
            Contract.Assert(r == null || (r.IsVisible == cell.IsVisible));
            return r;
        }

        public static CellAddress GetOpposite(Range selection, CellAddress activeAddress)
        {
            // todo: check nulls here
            if (activeAddress.Equals(selection.Address.BottomRight))
                activeAddress = selection.Address.TopLeft;
            else if (activeAddress.Equals(selection.Address.TopRight))
                activeAddress = selection.Address.BottomLeft;
            else if (activeAddress.Equals(selection.Address.BottomLeft))
                activeAddress = selection.Address.TopRight;
            else
                activeAddress = selection.Address.BottomRight;
            return activeAddress;
        }

    }
}